﻿// $Id: MithrilClient.cs 14 2008-08-21 21:31:25Z jkingry $

namespace Mithril.Client
{
    using System;
    using System.Json;
    using System.Net;
    using System.Net.Sockets;

    public class MithrilClient
    {
        public MithrilClient(string serviceBaseAddress, int messageSize)
        {
            this.serviceBaseAddress = serviceBaseAddress;

            this.messageSize = (byte) messageSize;
        }
        public MithrilClient(string serviceBaseAddress)
            : this(serviceBaseAddress, 1)
        {
        }

        public event EventHandler ConnectCompleted;
        public event EventHandler SendSignalCompleted;
        public event SignalRecievedEventHandler SignalRecieved;
        public event EventHandler ClientClosed;

        public string IpAddress
        {
            get { return this.IpAddress; }
        }
        public Guid Channel
        {
            get { return this.channel; }
        }

        public void ConnectAsync()
        {
            WebClient client = new WebClient();
            client.BaseAddress = this.serviceBaseAddress;
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Connect_DownloadStringCompleted);
            client.DownloadStringAsync(new Uri("list", UriKind.Relative));

        }

        public void ConnectAsync(string ipAddress, Guid channel)
        {
            this.ipAddress = ipAddress;
            this.channel = channel;

            IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipAddress), 4512);

            byte[] buffer = new byte[17];
            Buffer.BlockCopy(channel.ToByteArray(), 0, buffer, 0, 16);
            buffer[16] = this.messageSize;

            SocketAsyncEventArgs e = new SocketAsyncEventArgs();
            e.RemoteEndPoint = ep;
            e.SetBuffer(buffer, 0, 17);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(this.Connect_SocketConnectCompleted);

            if (!Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, e))
            {
                this.Connect_SocketConnectCompleted(null, e);
            }
        }

        public void SendSignalAsync(byte[] signal)
        {
            SocketAsyncEventArgs e = new SocketAsyncEventArgs();
            e.SetBuffer(signal, 0, this.messageSize);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(this.SendSignal_Completed);
            if (!this.sock.SendAsync(e))
            {
                this.SendSignal_Completed(this, e);
            }
        }

        protected void OnConnectCompleted()
        {
            WebClient client = new WebClient();
            client.BaseAddress = this.serviceBaseAddress;
            client.OpenReadAsync(new Uri("success?ip_address=" + this.ipAddress, UriKind.Relative));

            if (this.ConnectCompleted != null)
            {
                this.ConnectCompleted(this, EventArgs.Empty);
            }
        }

        protected void OnSendSignalCompleted()
        {
            if (this.SendSignalCompleted != null)
            {
                this.SendSignalCompleted(this, EventArgs.Empty);
            }
        }

        protected void OnSignalRecieved(byte[] signal)
        {
            if (this.SignalRecieved != null)
            {
                SignalRecievedEventArgs e = new SignalRecievedEventArgs(signal);
                this.SignalRecieved(this, e);
            }
        }

        protected void OnClientClosed()
        {
            this.sock.Close();
            this.sock = null;

            WebClient client = new WebClient();
            client.BaseAddress = this.serviceBaseAddress;
            client.OpenReadAsync(new Uri("failure?ip_address=" + this.ipAddress, UriKind.Relative));

            if (this.ClientClosed != null)
            {
                this.ClientClosed(this, EventArgs.Empty);
            }
        }

        private void Connect_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.OnClientClosed();
                return;
            }

            JsonValue servers = JsonObject.Parse(e.Result);
            JsonValue server = servers[random.Next(servers.Count)];

            this.ConnectAsync(server["ip_address"], Guid.NewGuid());
        }

        private void Connect_SocketConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                this.sock = e.ConnectSocket;
                this.OnConnectCompleted();

                this.RecieveAsync();
            }
            else
            {
                this.OnClientClosed();
            }
        }

        private void RecieveAsync()
        {
            byte[] buffer = new byte[this.messageSize];

            SocketAsyncEventArgs e = new SocketAsyncEventArgs();
            e.SetBuffer(buffer, 0, this.messageSize);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(this.Recieve_Completed);

            if (!this.sock.ReceiveAsync(e))
            {
                this.Recieve_Completed(this, e);
            }
        }
        
        private void Recieve_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                this.OnSignalRecieved(e.Buffer);

                this.RecieveAsync();
            }
        }

        private void SendSignal_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                this.OnSendSignalCompleted();
            }
            else
            {
                this.OnClientClosed();
            }
        }

        private readonly string serviceBaseAddress;
        private readonly Random random = new Random();
        private readonly byte messageSize;

        private string ipAddress;
        private Guid channel;
        private Socket sock;
    }

}
